﻿/**
*	Blackbird - Open Source JavaScript Logging Utility
*	Author: G Scott Olson
*	Web: http://blackbirdjs.googlecode.com/
*	     http://www.gscottolson.com/blackbirdjs/
*	Version: 1.0
*
*	The MIT License - Copyright (c) 2008 Blackbird Project
*
* 快捷键：
*		F2： 显示和隐藏控制台
*		Shift + F2 : 移动控制台，目前只支持移动到四个角。
*		Alt + Shift + F2：清空控制台信息
* 
* 共用函数：
*		log.toggle()          显示或隐藏 Blackbird
*		log.move()            移动
*		log.resize()          修改 Blackbird 窗口显示大小
*		log.clear()           清空信息
*		log.debug( message )  debug 信息
*		log.info( message )   一般消息
*		log.warn( message )   警告信息
*		log.error( message )  错误信息
*		log.profile( label )  计算消耗时间
**/

HmJS.register('log', function ($ns) {

	var NAMESPACE = 'log';
	var IE6_POSITION_FIXED = true; // enable IE6 {position:fixed}

	var bbird;
	var outputList;
	var cache = [];

	var state = getState();
	var classes = {};
	var profiler = {};
	var IDs = {
		blackbird: 'blackbird',
		checkbox: 'bbVis',
		filters: 'bbFilters',
		controls: 'bbControls',
		size: 'bbSize'
	};
	var messageTypes = { //order of these properties imply render order of filter controls
		debug: true,
		info: true,
		warn: true,
		error: true,
		profile: true
	};

	function generateMarkup() { //build markup
		var spans = [];
		for (type in messageTypes) {
			spans.push(['<span class="', type, '" type="', type, '"></span>'].join(''));
		}

		var newNode = document.createElement('DIV');
		newNode.id = IDs.blackbird;
		newNode.style.display = 'none';
		newNode.innerHTML = [
			'<div class="header">',
				'<div class="left">',
					'<div id="', IDs.filters, '" class="filters" title="click to filter by message type">', spans.join(''), '</div>',
				'</div>',
				'<div class="right">',
					'<div id="', IDs.controls, '" class="controls">',
						'<span id="', IDs.size, '" title="contract" op="resize"></span>',
						'<span class="clear" title="clear" op="clear"></span>',
						'<span class="close" title="close" op="close"></span>',
					'</div>',
				'</div>',
			'</div>',
			'<div class="main">',
				'<div class="left"></div><div class="mainBody">',
					'<ol>', cache.join(''), '</ol>',
				'</div><div class="right"></div>',
			'</div>',
			'<div class="footer">',
				'<div class="left"><label for="', IDs.checkbox, '"><input type="checkbox" id="', IDs.checkbox, '" />Visible on page load</label></div>',
				'<div class="right"></div>',
			'</div>'
		].join('');
		return newNode;
	}

	function backgroundImage() { //(IE6 only) change <BODY> tag's background to resolve {position:fixed} support
		var bodyTag = document.getElementsByTagName('BODY')[0];

		if (bodyTag.currentStyle && IE6_POSITION_FIXED) {
			if (bodyTag.currentStyle.backgroundImage == 'none') {
				bodyTag.style.backgroundImage = 'url(about:blank)';
			}
			if (bodyTag.currentStyle.backgroundAttachment == 'scroll') {
				bodyTag.style.backgroundAttachment = 'fixed';
			}
		}
	}

	function addMessage(type, content) { //adds a message to the output list
		content = (content.constructor == Array) ? content.join('') : content;
		if (outputList) {
			var newMsg = document.createElement('LI');
			newMsg.className = type;
			// newMsg.title = type;
			newMsg.innerHTML = ['<span class="icon"></span>', content].join('');
			outputList.appendChild(newMsg);
			scrollToBottom();
		} else {
			cache.push(['<li class="', type, '"><span class="icon"></span>', content, '</li>'].join(''));
		}
	}

	function clear() { //clear list output
		outputList.innerHTML = '';
	}

	function clickControl(evt) {
		if (!evt) evt = window.event;
		var el = (evt.target) ? evt.target : evt.srcElement;

		if (el.tagName == 'SPAN') {
			switch (el.getAttributeNode('op').nodeValue) {
				case 'resize': resize(); break;
				case 'clear': clear(); break;
				case 'close': hide(); break;
			}
		}
	}

	function clickFilter(evt) { //show/hide a specific message type
		if (!evt) evt = window.event;
		var span = (evt.target) ? evt.target : evt.srcElement;

		if (span && span.tagName == 'SPAN') {

			var type = span.getAttributeNode('type').nodeValue;

			if (evt.altKey) {
				var filters = document.getElementById(IDs.filters).getElementsByTagName('SPAN');

				var active = 0;
				for (entry in messageTypes) {
					if (messageTypes[entry]) active++;
				}
				var oneActiveFilter = (active == 1 && messageTypes[type]);

				for (var i = 0; filters[i]; i++) {
					var spanType = filters[i].getAttributeNode('type').nodeValue;

					filters[i].className = (oneActiveFilter || (spanType == type)) ? spanType : spanType + 'Disabled';
					messageTypes[spanType] = oneActiveFilter || (spanType == type);
				}
			}
			else {
				messageTypes[type] = !messageTypes[type];
				span.className = (messageTypes[type]) ? type : type + 'Disabled';
			}

			//build outputList's class from messageTypes object
			var disabledTypes = [];
			for (type in messageTypes) {
				if (!messageTypes[type]) disabledTypes.push(type);
			}
			disabledTypes.push('');
			outputList.className = disabledTypes.join('Hidden ');

			scrollToBottom();
		}
	}

	function clickVis(evt) {
		if (!evt) evt = window.event;
		var el = (evt.target) ? evt.target : evt.srcElement;

		state.load = el.checked;
		setState();
	}


	function scrollToBottom() { //scroll list output to the bottom
		outputList.scrollTop = outputList.scrollHeight;
	}

	function isVisible() { //determine the visibility
		return (bbird.style.display == 'block');
	}

	function hide() {
		bbird.style.display = 'none';
	}

	function show() {
		var body = document.getElementsByTagName('BODY')[0];
		body.removeChild(bbird);
		body.appendChild(bbird);
		bbird.style.display = 'block';
	}

	//sets the position
	function reposition(position) {
		if (position === undefined || position == null) {
			position = (state && state.pos === null) ? 1 : (state.pos + 1) % 4; //set to initial position ('topRight') or move to next position
		}

		switch (position) {
			case 0: classes[0] = 'bbTopLeft'; break;
			case 1: classes[0] = 'bbTopRight'; break;
			case 2: classes[0] = 'bbBottomLeft'; break;
			case 3: classes[0] = 'bbBottomRight'; break;
		}
		state.pos = position;
		setState();
	}

	function resize(size) {
		if (size === undefined || size === null) {
			size = (state && state.size == null) ? 0 : (state.size + 1) % 2;
		}

		classes[1] = (size === 0) ? 'bbSmall' : 'bbLarge'

		var span = document.getElementById(IDs.size);
		span.title = (size === 1) ? 'small' : 'large';
		span.className = span.title;

		state.size = size;
		setState();
		scrollToBottom();
	}

	function setState() {
		var props = [];
		for (entry in state) {
			var value = (state[entry] && state[entry].constructor === String) ? '"' + state[entry] + '"' : state[entry];
			props.push(entry + ':' + value);
		}
		props = props.join(',');

		var expiration = new Date();
		expiration.setDate(expiration.getDate() + 14);
		document.cookie = ['blackbird={', props, '}; expires=', expiration.toUTCString(), ';'].join('');

		var newClass = [];
		for (word in classes) {
			newClass.push(classes[word]);
		}
		bbird.className = newClass.join(' ');
	}

	function getState() {
		var re = new RegExp(/blackbird=({[^;]+})(;|\b|$)/);
		var match = re.exec(document.cookie);
		return (match && match[1]) ? eval('(' + match[1] + ')') : { pos: null, size: null, load: null };
	}

	//event handler for 'keyup' event for window
	function readKey(evt) {
		if (!evt) evt = window.event;
		var code = 113; //F2 key

		if (evt && evt.keyCode == code) {

			var visible = isVisible();

			if (visible && evt.shiftKey && evt.altKey) clear();
			else if (visible && evt.shiftKey) reposition();
			else if (!evt.shiftKey && !evt.altKey) {
				(visible) ? hide() : show();
			}
		}
	}

	//event management ( thanks John Resig )
	function addEvent(obj, type, fn) {
		var obj = (obj.constructor === String) ? document.getElementById(obj) : obj;
		if (obj.attachEvent) {
			obj['e' + type + fn] = fn;
			obj[type + fn] = function () { obj['e' + type + fn](window.event) };
			obj.attachEvent('on' + type, obj[type + fn]);
		} else obj.addEventListener(type, fn, false);
	}
	function removeEvent(obj, type, fn) {
		var obj = (obj.constructor === String) ? document.getElementById(obj) : obj;
		if (obj.detachEvent) {
			obj.detachEvent('on' + type, obj[type + fn]);
			obj[type + fn] = null;
		} else obj.removeEventListener(type, fn, false);
	}

	window[NAMESPACE] = {
		toggle:
			function () { (isVisible()) ? hide() : show(); },
		resize:
			function () { resize(); },
		clear:
			function () { clear(); },
		move:
			function () { reposition(); },
		debug:
			function (msg) { addMessage('debug', msg); },
		warn:
			function (msg) { addMessage('warn', msg); },
		info:
			function (msg) { addMessage('info', msg); },
		error:
			function (msg) { addMessage('error', msg); },
		profile:
			function (label) {
				var currentTime = new Date(); //record the current time when profile() is executed

				if (label == undefined || label == '') {
					addMessage('error', '<b>ERROR:</b> Please specify a label for your profile statement');
				}
				else if (profiler[label]) {
					addMessage('profile', [label, ': ', currentTime - profiler[label], 'ms'].join(''));
					delete profiler[label];
				}
				else {
					profiler[label] = currentTime;
					addMessage('profile', label);
				}
				return currentTime;
			}
	};

	addEvent(window, 'load',
	/* initialize Blackbird when the page loads */
		function () {
			var body = document.getElementsByTagName('BODY')[0];
			bbird = body.appendChild(generateMarkup());
			outputList = bbird.getElementsByTagName('OL')[0];

			backgroundImage();

			//add events
			addEvent(IDs.checkbox, 'click', clickVis);
			addEvent(IDs.filters, 'click', clickFilter);
			addEvent(IDs.controls, 'click', clickControl);
			addEvent(document, 'keyup', readKey);

			resize(state.size);
			reposition(state.pos);
			if (state.load) {
				show();
				document.getElementById(IDs.checkbox).checked = true;
			}

			scrollToBottom();

			window[NAMESPACE].init = function () {
				show();
				window[NAMESPACE].error(['<b>', NAMESPACE, '</b> can only be initialized once']);
			};

			addEvent(window, 'unload', function () {
				removeEvent(IDs.checkbox, 'click', clickVis);
				removeEvent(IDs.filters, 'click', clickFilter);
				removeEvent(IDs.controls, 'click', clickControl);
				removeEvent(document, 'keyup', readKey);
			});
		});

});
